class: center, middle, inverse, title-slide .title[ # ISA 444/544: Business Forecasting ] .subtitle[ ## 06: Centered Moving Average and Seasonal Decomposition ] .author[ ###
Fadel M. Megahed, PhD
Raymond E. Glos Professor in Business
Farmer School of Business
Miami University
@FadelMegahed
fmegahed
fmegahed@miamioh.edu
Automated Scheduler for Office Hours
] .date[ ### Fall 2025 ] --- ## Quick Refresher of Last Class ✅ Generate and interpret simple line charts ✅ Create seasonal plots and subplots ✅ Describe a lag and create lag scatter plots ✅ Plot and interpret the autocorrelation function (ACF) for a time-series --- ## Learning Objectives for Today's Class - Describe and compute centered moving averages - Estimate trend-cycle via moving averages - Perform classical decomposition (trend-cycle, seasonal, residual/remainder) - Understand STL / MSTL as alternatives to classical decomposition --- class: inverse, center, middle # Motivation and Context --- ## Recall: Time Series Components - **Trend**: Long-term increase or decrease in the data - **Seasonal**: Regular pattern of up and down fluctuations influenced by calendar or time of day (e.g., quarterly, monthly, day of the week, hourly) - **Cyclical**: Repeating up and down movements that are **not of a fixed period** (e.g., business cycles, economic cycles), typically over multiple years --- ## Time Series Decomposition - **Decomposition** is the process of breaking down a time series into its constituent components: trend-cycle, seasonal, and residual/remainder. - **Mathematically**, we can write a time series `\(y_t\)` as a function of these components: `$$y_t = f(T_t, \ S_t, \ R_t),$$` where: + `\(T_t\)`: Trend-cycle component + `\(S_t\)`: Seasonal component + `\(R_t\)`: Remainder component + `\(f()\)`: Some function that combines the components - The decomposition can be **additive** or **multiplicative**. --- ## Seasonality: Additive Models **Definition:** An additive model is appropriate when the **trend is approximately linear**, and the **seasonal components stays constant over time.** It can be written as: `\(y_t = T_t + S_t + R_t\)`. <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/additive-1.png" width="100%" style="display: block; margin: auto;" /> --- ## Seasonality: Multiplicative Models **Definition:** A multiplicative model is appropriate when the **trend is nonlinear (e.g., exponential)**, and/or the **seasonal component changes proportionally with the level of the time series.** It can be written as: `\(y_t = T_t \times S_t \times R_t\)`. <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/multiplicative-3.png" width="100%" style="display: block; margin: auto;" /> --- ## Motivating Example: US Retail Trade Employment .panelset[ .panel[.panel-name[Data] - The [data](https://fred.stlouisfed.org/series/CEU4200000001) contains the **total employment in U.S. retail trade** sector from 1992 to 2025. - The [data](https://fred.stlouisfed.org/series/CEU4200000001) is **monthly** and **not seasonally adjusted**. <img src="data:image/png;base64,#../../figures/fred_us_retail.png" width="90%" style="display: block; margin: auto;" /> ] .panel[.panel-name[Plot] <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/retail_emp-1.png" width="100%" style="display: block; margin: auto;" /> ] .panel[.panel-name[Decomp] .font80[ .pull-left[ - **Decomposition** is the process of breaking down a time series into its constituent components: trend-cycle, seasonal, and residual/remainder. - Here, I am using the **classical decomposition method to decompose** the retail trade employment time series into its components. - We will talk about how that is done **later**. - What I want you to focus on is **understanding the charts** and the following **concepts**: + Trend-Cycle + DeTrended Series + Seasonally Adjusted Series ] .pull-right[ |Date | `\(y_t\)`| `\(T_t\)`| `\(S_t\)`| `\(R_t\)`| |:----------|-------:|-------:|------:|------:| |1992-01-01 | 12784.3| NA| -61.7| NA| |1992-02-01 | 12581.9| NA| -238.6| NA| |1992-03-01 | 12543.3| NA| -220.0| NA| |1992-04-01 | 12632.0| NA| -232.7| NA| |1992-05-01 | 12735.4| NA| -125.1| NA| |1992-06-01 | 12819.7| NA| -17.3| NA| |1992-07-01 | 12799.8| 12828.3| -12.0| -16.5| |1992-08-01 | 12805.1| 12835.5| -9.5| -20.9| |1992-09-01 | 12778.3| 12844.6| -92.0| 25.7| |1992-10-01 | 12884.5| 12853.4| 41.7| -10.6| |1992-11-01 | 13141.0| 12862.8| 401.8| -123.6| ] ] ] .panel[.panel-name[Decomp] <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/decompose_retail_emp2-1.png" width="100%" style="display: block; margin: auto;" /> ] .panel[.panel-name[Trend-Cyc] <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/tc_overlay_retail_emp-3.png" width="100%" style="display: block; margin: auto;" /> ] .panel[.panel-name[DeTrend] <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/detrend_retail_emp-5.png" width="100%" style="display: block; margin: auto;" /> .center[.font50[**Note:** The detrended series is obtained by subtracting the trend-cycle component from the observed TS, i.e., `\(y_t - T_t\)`.]] ] .panel[.panel-name[SeasAdj] <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/deseasonalize_retail_emp-7.png" width="100%" style="display: block; margin: auto;" /> .center[.font50[**Note:** The deasonalized series is obtained by subtracting the seasonal component from the observed TS `\(y_t - S_t\)`. It is similar to the seasonally adjusted [USTRADE](https://fred.stlouisfed.org/series/USTRADE) series.]] ] .panel[.panel-name[Notes] .can-edit.key-activity5[ - Edit me - ... - ... - ... - ... - ... - ... ] ] ] ??? From <https://otexts.com/fpp3/components.html> Seasonally adjusted series contain the remainder component as well as the trend-cycle. Therefore, they are not “smooth”, and “downturns” or “upturns” can be misleading. If the purpose is to look for turning points in a series, and interpret any changes in direction, then it is better to use the trend-cycle component rather than the seasonally adjusted data. --- class: inverse, center, middle # Centered Moving Averages --- ## Relation of Moving Averages to Classical Decomposition - The **classical method of time series decomposition** originated in the 1920s and was widely used until the 1950s. - It still **forms the basis of many time series decomposition methods**, so it is important to understand how it works. - The **first step in a classical decomposition is to use a moving average method to estimate the trend-cycle**, so we begin by discussing moving averages. .footnote[ <html> <hr> </html> **Source:** Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. <https://otexts.com/fpp3/>. Accessed on February 12, 2025. [Chapter 3.3](https://otexts.com/fpp3/moving-averages.html) ] --- ## Moving Average (MA) Smoothing - **Moving averages** are used to **smooth out short-term fluctuations** and highlight longer-term trends or cycles. - In classical decomposition, the **trend-cycle component is estimated using a centered moving average**. - A **centered moving average** is calculated by taking the average of values on either side of the data point in question. - Mathematically,a centered moving average of order `\(m\)` can be written as: `$$\hat{T}_{t} = \frac{1}{m} \sum_{j=-k}^k y_{t+j},$$` where `\(m = 2k + 1\)` is the number of values in the moving average. --- ## Activity: Centered Moving Average
−
+
03
:
00
Let's consider the following series: `\(y_t = \{4, 7, 7, 10, 13, 13, 16\}\)` Calculate a **centered moving average of order 3**, and **input it in the CMA3 column below.** + Note each **cell** in the table is **editable**, but please **only edit the CMA3 column**. .font80[
] --- ## Centered Moving Averages in Python ``` python y_t = [4, 7, 7, 10, 13, 13, 16] df = ( pd.DataFrame({"t": range(1, len(y_t) + 1), "y_t": y_t}) .assign( cma3 = lambda x: x['y_t'].rolling(window = 3, center = True).mean(), cma4r = lambda x: x['y_t'].rolling(window = 4, center = True).mean(), cma4l = lambda x: x['cma4r'].shift(-1) ) ) df.head(7) ``` ``` ## t y_t cma3 cma4r cma4l ## 0 1 4 NaN NaN NaN ## 1 2 7 6.0 NaN 7.00 ## 2 3 7 8.0 7.00 9.25 ## 3 4 10 10.0 9.25 10.75 ## 4 5 13 12.0 10.75 13.00 ## 5 6 13 14.0 13.00 NaN ## 6 7 16 NaN NaN NaN ``` **Which of the two CMA4 cols is correct?** .can-edit.key-activity6[Edit me] --- ## Centered Moving Averages as Trend-Cycle Estimators <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/cma3_plot-1.png" width="100%" style="display: block; margin: auto;" /> --- ## Moving Average of Moving Averages When `\(m\)` is even, we often resort to using a **moving average of moving averages** to center the moving average. Let us revisit our previous example: .font90[ ``` python df = ( pd.DataFrame({"t": range(1, len(y_t) + 1), "y_t": y_t}) .assign( cma3 = lambda x: x['y_t'].rolling(window = 3, center = True).mean(), cma4r = lambda x: x['y_t'].rolling(window = 4, center = True).mean(), cma4l = lambda x: x['cma4r'].shift(-1), * cma4_2 = lambda x: x['cma4l'].rolling(window = 2, center = True).mean(), * cma4_2_alt = lambda x: (x['cma4r'] + x['cma4l'])/2 ) ) df.head(7) ``` ``` ## t y_t cma3 cma4r cma4l cma4_2 cma4_2_alt ## 0 1 4 NaN NaN NaN NaN NaN ## 1 2 7 6.0 NaN 7.00 NaN NaN ## 2 3 7 8.0 7.00 9.25 8.125 8.125 ## 3 4 10 10.0 9.25 10.75 10.000 10.000 ## 4 5 13 12.0 10.75 13.00 11.875 11.875 ## 5 6 13 14.0 13.00 NaN NaN NaN ## 6 7 16 NaN NaN NaN NaN NaN ``` ] --- ## Moving Average of Moving Averages (Cont.) When a 2-MA follows a moving average of an even order (such as 4), it is called a “centered moving average of order 4”. This is because the results are now symmetric. To see that this is the case, we can write the `\(2\times 4\)`-MA (named as `cma4_2` or `cma4_2_alt` in our Python code) as follows: $$ `\begin{align*} \hat{T}_{t} &= \frac{1}{2}\Big[ \frac{1}{4} (y_{t-2}+y_{t-1}+y_{t}+y_{t+1}) + \frac{1}{4} (y_{t-1}+y_{t}+y_{t+1}+y_{t+2})\Big] \\ &= \frac{1}{8}y_{t-2}+\frac14y_{t-1} + \frac14y_{t}+\frac14y_{t+1}+\frac18y_{t+2}. \end{align*}` $$ It is now a **weighted average of observations that is symmetric**. .footnote[ <html> <hr> </html> **Source:** Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. <https://otexts.com/fpp3/>. Accessed on February 12, 2025. [Chapter 3.3](https://otexts.com/fpp3/moving-averages.html) ] --- ## Centered Moving Averages for Trend-Cycle Estimation 📊 **Key Idea:** Centered moving averages (CMAs) help estimate the **trend-cycle** by smoothing seasonal fluctuations. <br> **Example: 2 × 4 - MA** `$$\hat{T}_t = \frac{1}{8}y_{t-2} + \frac{1}{4}y_{t-1} + \frac{1}{4}y_t + \frac{1}{4}y_{t+1} + \frac{1}{8}y_{t+2}$$` <br> ✅ **Balances** data across quarters ✅ **Averages out seasonal /quarterly variation** --- ## Choosing the Right Moving Average | **Seasonal Period (m)** | **Recommended MA** | |----------------|----------------| | **Even** (e.g., 12 months, 4 quarters) | **2 × m - MA** | | **Odd** (e.g., 7 days) | **m-MA** | <br> ✔ **2 × 12 - MA** for monthly seasonality in annual data ✔ **2 × 4 - MA** for quarterly seasonality in annual data ✔ **7-MA** for weekly seasonality in daily data <br> ⚠ **Beware!** Using the wrong MA order **retains seasonal noise** instead of extracting the trend. --- ## Activity: Trend-Cycle Estimation Using CMAs
−
+
05
:
00
.panelset[ .panel[.panel-name[Data] - Download the [CEU4200000001](https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23ebf3fb&chart_type=line&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1320&nt=0&thu=0&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=USTRADE&scale=left&cosd=1992-01-01&coed=2025-01-01&line_color=%230073e6&link_values=false&line_style=solid&mark_type=none&mw=3&lw=3&ost=-99999&oet=99999&mma=0&fml=a&fq=Monthly&fam=avg&fgst=lin&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2025-02-12&revision_date=2025-02-12&nd=1939-01-01), capturing the **total employment in U.S. retail trade** sector from 1992 to 2025. - The data is **monthly** and **not seasonally adjusted**. - Use an **appropriate moving average** to estimate the trend-cycle component. - Recreate the plot of raw series and the trend-cycle component from the next tab. ] .panel[.panel-name[Plot] <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/retail_empl_cma-3.png" width="100%" style="display: block; margin: auto;" /> ] .panel[.panel-name[Printout] .font80[ ``` ## date CEU4200000001 month cmar cmal cma_2 ## 0 1992-01-01 12784.3 January NaN NaN NaN ## 1 1992-02-01 12581.9 February NaN NaN NaN ## 2 1992-03-01 12543.3 March NaN NaN NaN ## 3 1992-04-01 12632.0 April NaN NaN NaN ## 4 1992-05-01 12735.4 May NaN NaN NaN ## 5 1992-06-01 12819.7 June NaN 12825.9 NaN ## 6 1992-07-01 12799.8 July 12825.9 12830.8 12828.35 ## 7 1992-08-01 12805.1 August 12830.8 12840.2 12835.5 ## 8 1992-09-01 12778.3 September 12840.2 12848.9 12844.55 ## 9 1992-10-01 12884.5 October 12848.9 12857.8 12853.35 ## ... ... ... ... ... ... ... ## 387 2024-04-01 15406.9 April 15536.4 15534.6 15535.5 ## 388 2024-05-01 15487.1 May 15534.6 15532.0 15533.3 ## 389 2024-06-01 15557.3 June 15532.0 15532.0 15532.0 ## 390 2024-07-01 15540.0 July 15532.0 15536.1 15534.05 ## 391 2024-08-01 15478.8 August 15536.1 NaN NaN ## 392 2024-09-01 15410.9 September NaN NaN NaN ## 393 2024-10-01 15534.6 October NaN NaN NaN ## 394 2024-11-01 15812.0 November NaN NaN NaN ## 395 2024-12-01 15954.0 December NaN NaN NaN ## 396 2025-01-01 15517.7 January NaN NaN NaN ``` ] ] ] --- class: inverse, center, middle # Classical Decomposition --- ## Classical Decomposition .content-box-grey[ **Additive decomposition:** `\(y_t = T_t+S_t+R_t = \hat{T}_t + \hat{S}_t + \hat{R}_t\)` **Multiplicative decomposition:** `\(y_t = T_t\times S_t\times R_t = \hat{T}_t\times \hat{S}_t\times\hat{R}_t\)` ] - **Estimate** `\(\hat{T}\)` using `\((2\times m)\)`-MA if `\(m\)` is even. Otherwise, estimate `\(\hat{T}\)` using `\(m\)`-MA - **Compute de-trended series** using: (a) `\(y_t - \hat{T}_t\)` for additive decomposition, and (b) `\(y_t/\hat{T}_t\)` for multiplicative decomposition. - Once de-trended, **we are left with** the `\(S_t\)` and `\(R_t\)` components. Specifically: * **Additive model:** `\(y_t-\hat{T}_t = (\hat{T}_t+\hat{S}_t+\hat{R}_t) - \hat{T}_t = \hat{S}_t+\hat{R}_t\)` * **Multiplicative model:** `\(\frac{y_t}{\hat{T}_t} = \frac{\hat{T}_t\times \hat{S}_t\times \hat{R}_t}{\hat{T}_t} = \hat{S}_t\times \hat{R}_t\)` .footnote[ <html> <hr> </html> **Source:** Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. <https://otexts.com/fpp3/>. Accessed on February 12, 2025. [Chapter 3.4 Slides](https://github.com/robjhyndman/fpp3_slides/blob/main/3-4-classical-decomposition.Rmd) ] --- ## Estimating Seasonal Component - The **seasonal component** can be estimated by **averaging** the **detrended series** for that season across all years. - For example, if we have monthly data, we can estimate the seasonal index for January by averaging all the January values in the detrended series. Do this for each month to get the seasonal index. - **Adjust** the seasonal indices to ensure that that: * for additive: `\(S^{(1)}+S^{(2)}+\ldots+S^{(12)}=0\)` * for multiplicative: `\(S^{(1)}+S^{(2)}+\ldots+S^{(12)}=m\)` - The **seasonal component** is then obtained by **repeating** the **seasonal indices** for each year in the data. .footnote[ <html> <hr> </html> **Source:** Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. <https://otexts.com/fpp3/>. Accessed on February 12, 2025. [Chapter 3.4 Slides](https://github.com/robjhyndman/fpp3_slides/blob/main/3-4-classical-decomposition.Rmd) ] --- ## Estimating the Remainder Component Additive decomposition: `\(\hat{R}_t = y_t - \hat{T}_t - \hat{S}_t\)` Multiplicative decomposition: `\(\hat{R}_t = y_t / (\hat{T}_t\hat{S}_t)\)` --- ## Classical Decomposition in Python (Ref) <iframe src="https://www.statsmodels.org/devel/generated/statsmodels.tsa.seasonal.seasonal_decompose.html" width="100%" height="500px" data-external="1"></iframe> --- ## Classical Decomposition in Python (Code-Add) .font90[ ``` python from statsmodels.tsa.seasonal import seasonal_decompose retail_empl = ( pd.read_csv("https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23ebf3fb&chart_type=line&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1320&nt=0&thu=0&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=CEU4200000001&scale=left&cosd=1992-01-01&coed=2025-01-01&line_color=%230073e6&link_values=false&line_style=solid&mark_type=none&mw=3&lw=3&ost=-99999&oet=99999&mma=0&fml=a&fq=Monthly&fam=avg&fgst=lin&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2025-02-12&revision_date=2025-02-12&nd=1939-01-01") .rename(columns = {"observation_date": "date", "CEU4200000001": "CEU4200000001"}) .assign( date = lambda x: pd.to_datetime(x["date"]), ) ) # Set the date as index and set the frequency of the data retail_empl = retail_empl.set_index("date") retail_empl = retail_empl.asfreq("MS") # Decompose the time series res = seasonal_decompose( retail_empl["CEU4200000001"], model = "additive") # Extract the trend, seasonal, and residual components res_df = pd.DataFrame({ "observed": res.observed, "trend": res.trend, "seasonal": res.seasonal, "residual": res.resid }) # Plot the decomposed time series fig = res.plot() fig.set_size_inches(10, 4.75) fig.tight_layout() ``` <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/classical_decomp_py_add-1.png" width="960" style="display: block; margin: auto;" /> ] --- ## Classical Decomposition in Python (Output-Add) <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/classical_decomp_py_out_add-3.png" width="100%" style="display: block; margin: auto;" /> .footnote[ <html> <hr> </html> **Comment:** You can have more control over the plot by plotting the data frame `res_df` directly. See this example from [StackOverflow](https://stackoverflow.com/a/55350230/10156153), while noting that their `decomposition` is equivalent to our `res` object. You can also convert the data into a long format using `pd.melt`, then `.reset_index()` to convert date into a column, and plot it using `sns.relplot`. ] --- ## Classical Decomposition in Python (Code-Mult) .font90[ ``` python from statsmodels.tsa.seasonal import seasonal_decompose retail_empl = ( pd.read_csv("https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23ebf3fb&chart_type=line&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1320&nt=0&thu=0&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=CEU4200000001&scale=left&cosd=1992-01-01&coed=2025-01-01&line_color=%230073e6&link_values=false&line_style=solid&mark_type=none&mw=3&lw=3&ost=-99999&oet=99999&mma=0&fml=a&fq=Monthly&fam=avg&fgst=lin&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2025-02-12&revision_date=2025-02-12&nd=1939-01-01") .rename(columns = {"observation_date": "date", "CEU4200000001": "CEU4200000001"}) .assign( date = lambda x: pd.to_datetime(x["date"]), ) ) # Set the date as index and set the frequency of the data retail_empl = retail_empl.set_index("date") retail_empl = retail_empl.asfreq("MS") # Decompose the time series res = seasonal_decompose( retail_empl["CEU4200000001"], model = "multiplicative") # Extract the trend, seasonal, and residual components res_df = pd.DataFrame({ "observed": res.observed, "trend": res.trend, "seasonal": res.seasonal, "residual": res.resid }) # Plot the decomposed time series fig = res.plot() fig.set_size_inches(10, 4.75) fig.tight_layout() ``` <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/classical_decomp_py_mult-5.png" width="960" style="display: block; margin: auto;" /> ] --- ## Classical Decomposition in Python (Output-Mult) <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/classical_decomp_py_out_mult-7.png" width="100%" style="display: block; margin: auto;" /> .footnote[ <html> <hr> </html> **Comment:** You can have more control over the plot by plotting the data frame `res_df` directly. See this example from [StackOverflow](https://stackoverflow.com/a/55350230/10156153), while noting that their `decomposition` is equivalent to our `res` object. You can also convert the data into a long format using `pd.melt`, then `.reset_index()` to convert date into a column, and plot it using `sns.relplot`. ] --- ## Comments on the Classical Decomposition - Estimate of trend is **unavailable** for first few and last few observations. - The **seasonal component repeats** from year to year, which may not be realistic (especially for long time-series). - Since the trend-cycle is estimated using an average-based method, it is **not robust to outliers**. - **Newer methods** are designed to overcome these problems. .footnote[ <html> <hr> </html> **Source:** Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. <https://otexts.com/fpp3/>. Accessed on February 12, 2025. [Chapter 3.4 Slides](https://github.com/robjhyndman/fpp3_slides/blob/main/3-4-classical-decomposition.Rmd) ] --- class: inverse, center, middle # STL Decomposition --- ## Seasonal-Trend Decomposition using LOESS (STL) - **STL** is a very flexible method for decomposing time series. - It can handle **any type of seasonality**, and allows for **non-constant** seasonal patterns. - It is robust to **outliers**. - However, it: - **cannot** handle trading day or calendar adjustments, and - it is only **additive** (take logs to get multiplicative decomposition). --- ## STL Decomposition in Python (Ref) <iframe src="https://www.statsmodels.org/devel/generated/statsmodels.tsa.seasonal.STL.html" width="100%" height="500px" data-external="1"></iframe> --- ## STL Decomposition in Python (Code) .font90[ ``` python from statsmodels.tsa.seasonal import STL retail_empl = ( pd.read_csv("https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23ebf3fb&chart_type=line&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1320&nt=0&thu=0&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=CEU4200000001&scale=left&cosd=1992-01-01&coed=2025-01-01&line_color=%230073e6&link_values=false&line_style=solid&mark_type=none&mw=3&lw=3&ost=-99999&oet=99999&mma=0&fml=a&fq=Monthly&fam=avg&fgst=lin&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2025-02-12&revision_date=2025-02-12&nd=1939-01-01") .rename(columns = {"observation_date": "date", "CEU4200000001": "CEU4200000001"}) .assign( date = lambda x: pd.to_datetime(x["date"]), ) ) retail_empl = retail_empl.set_index("date") retail_empl = retail_empl.asfreq("MS") # Decompose the time series *stl = STL(retail_empl["CEU4200000001"], seasonal = 13, robust = True) *res = stl.fit() # Extract the trend, seasonal, and residual components res_df = pd.DataFrame({ "observed": res.observed, "trend": res.trend, "seasonal": res.seasonal, "residual": res.resid }) # Plot the decomposed time series fig = res.plot() fig.set_size_inches(10, 4.75) fig.tight_layout() ``` <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/stl_decomp_py-1.png" width="960" style="display: block; margin: auto;" /> ] --- ## STL Decomposition in Python (Out) <img src="data:image/png;base64,#06_cma_and_decomposition_files/figure-html/stl_decomp_py_out-3.png" width="100%" style="display: block; margin: auto;" /> --- ## STL Decomposition vs Classical Decomposition
−
+
02
:
00
.black[.bold[In two minutes, examine the differences between the output figures from the classical decomposition (additive) and the STL decomposition.]] .can-edit.key-activity7[ - Edit me - ... - ... - ... - ... - ... ] --- class: inverse, center, middle # Recap --- ## Summary of Main Points By now, you should be able to do the following: - Describe and compute centered moving averages - Estimate trend-cycle via moving averages - Perform classical decomposition (trend-cycle, seasonal, residual/remainder) - Understand STL / MSTL as alternatives to classical decomposition --- ## 📝 Review and Clarification 📝 1. **Class Notes**: Take some time to revisit your class notes for key insights and concepts. 2. **Zoom Recording**: The recording of today's class will be made available on Canvas approximately 3-4 hours after the session ends. 3. **Questions**: Please don't hesitate to ask for clarification on any topics discussed in class. It's crucial not to let questions accumulate. --- ## 🎯 Assignment 🎯 - In preparation for the assignment, you are encouraged to thoroughly read the following: - [MSTL API Reference](https://www.statsmodels.org/devel/generated/statsmodels.tsa.seasonal.MSTL.html) from the statsmodels package. - [Multi-Seasonal Time Series Decomposition using MSTL in Python](https://towardsdatascience.com/multi-seasonal-time-series-decomposition-using-mstl-in-python-136630e67530/), a Towards Data Science Blog, which demonstrates how the MSTL method works and can be implemented on a similar dataset. - Skim through the [MSTL Research Paper](https://arxiv.org/pdf/2107.13462). - Once you read the references and went over our class notes, you are now ready to examine and complete [Assignment 04](https://miamioh.instructure.com/courses/240425/assignments/3256295).